﻿namespace AgentJohnson
{
  using System;
  using System.Linq;
  using System.Text.RegularExpressions;
  using JetBrains.Annotations;
  using JetBrains.ReSharper.Feature.Services.CSharp.Bulbs;
  using Resharper.Abstractions.Actions;
  using Resharper.Abstractions.CodeDom.Expressions;
  using Resharper.Abstractions.CodeDom.Expressions.BinaryExpressions;
  using Resharper.Abstractions.CodeDom.Expressions.BinaryExpressions.RelationalExpressions;
  using Resharper.Abstractions.CodeDom.Expressions.ExpressionInitializers;
  using Resharper.Abstractions.CodeDom.Expressions.PostFixOperatorExpressions;
  using Resharper.Abstractions.CodeDom.Statements.ForStatements;

  /// <summary>Defines the <see cref="ReverseForLoop"/> class.</summary>
  [ContextAction("Reverse For-Loop [Agent Johnson 2]", "Reverses the direction of a for-loop.")]
  public class ReverseForLoop : ContextActionBase
  {
    #region Constructors and Destructors

    /// <summary>Initializes a new instance of the <see cref="ReverseForLoop"/> class.</summary>
    /// <param name="provider">The provider.</param>
    public ReverseForLoop([NotNull] ICSharpContextActionDataProvider provider) : base(provider)
    {
    }

    #endregion

    #region Methods

    /// <summary>Determines whether this instance can execute the specified action context.</summary>
    /// <param name="context">The action context.</param>
    /// <returns><c>true</c> if this instance can execute the specified action context; otherwise, <c>false</c>.</returns>
    protected override bool CanExecute(CanExecuteContext context)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      return this.GetModel(context) != null;
    }

    /// <summary>Executes the action.</summary>
    /// <param name="context">The execute context.</param>
    protected override void Execute(ExecuteContext context)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      var model = this.GetModel(context);
      if (model == null)
      {
        return;
      }

      Expression exp = null;

      if (model.PostfixOperatorExpression.OperatorType == PostFixOperatorType.PlusPlus)
      {
        var expression = AddToExpression(context, model.To, '-');

        model.ExpressionInitializer.Value = expression;

        var condition = this.GetCondition(context, model.RelationalExpression.LeftOperand, model.RelationalExpression.OperatorSign, model.From);

        model.RelationalExpression.ReplaceBy(condition);

        exp = context.CreateExpression(string.Format("{0}--", model.PostfixOperatorExpression.Operand));
      }
      else
      {
        var expression = AddToExpression(context, model.From, '+');

        model.ExpressionInitializer.Value = model.To;

        var condition = this.GetCondition(context, model.RelationalExpression.LeftOperand, model.RelationalExpression.OperatorSign, expression);

        model.RelationalExpression.ReplaceBy(condition);

        exp = context.CreateExpression(string.Format("{0}++", model.PostfixOperatorExpression.Operand));
      }

      if (exp == null)
      {
        return;
      }

      var result = model.PostfixOperatorExpression.ReplaceBy(exp);
      context.Formatter.Format(result);
    }

    /// <summary>Gets the text.</summary>
    /// <returns>Returns the text.</returns>
    protected override string GetText()
    {
      return "Reverse for-loop [Agent Johnson]";
    }

    /// <summary>Adds to expression.</summary>
    /// <param name="context">The context.</param>
    /// <param name="expression">The expression.</param>
    /// <param name="sign">The sign.</param>
    /// <returns>Returns the to expression.</returns>
    private static Expression AddToExpression([NotNull] ExecuteContext context, [NotNull] Expression expression, char sign)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      if (expression == null)
      {
        throw new ArgumentNullException("expression");
      }

      var sign2 = sign == '-' ? '+' : '-';

      var code = expression.ToString();
      if (code.StartsWith("(") && code.EndsWith(")"))
      {
        code = code.Substring(1, code.Length - 2);
      }

      var match = Regex.Match(code, "\\" + sign2 + "\\s*1\\s*$");
      if (match.Success)
      {
        code = code.Substring(0, code.Length - match.Value.Length).Trim();

        if (code.StartsWith("(") && code.EndsWith(")"))
        {
          code = code.Substring(1, code.Length - 2);
        }
      }
      else
      {
        if (expression is BinaryExpression)
        {
          code = "(" + code + ") " + sign + " 1";
        }
        else
        {
          code += sign + " 1";
        }
      }

      code = code.Trim();

      return context.CreateExpression(code);
    }

    /// <summary>Gets the condition.</summary>
    /// <param name="context">The context.</param>
    /// <param name="leftOperand">The left operand.</param>
    /// <param name="operatorSign">The operator sign.</param>
    /// <param name="rightOperand">The right operand.</param>
    /// <returns>Returns the condition.</returns>
    private Expression GetCondition(ExecuteContext context, Expression leftOperand, string operatorSign, Expression rightOperand)
    {
      switch (operatorSign)
      {
        case "<":
          operatorSign = ">=";
          break;
        case ">":
          operatorSign = "<=";
          break;
        case "<=":
          operatorSign = ">";
          break;
        case ">=":
          operatorSign = "<";
          break;
      }

      return context.CreateExpression(string.Format("{0} {1} {2}", leftOperand, operatorSign, rightOperand));
    }

    /// <summary>Gets the model.</summary>
    /// <param name="context">The context.</param>
    /// <returns>Returns the model.</returns>
    private Model GetModel(ProviderContext context)
    {
      var forStatement = context.GetSelectedForStatement();
      if (forStatement == null)
      {
        return null;
      }

      var localVariable = forStatement.InitializedVariables.FirstOrDefault();
      if (localVariable == null)
      {
        return null;
      }

      if (localVariable.TypeName != "int")
      {
        return null;
      }

      var expressionInitializer = localVariable.Initializer;
      if (expressionInitializer == null)
      {
        return null;
      }

      var from = expressionInitializer.Value;
      if (from == null)
      {
        return null;
      }

      var relationalExpression = forStatement.Condition as RelationalExpression;
      if (relationalExpression == null)
      {
        return null;
      }

      var to = relationalExpression.RightOperand;
      if (to == null)
      {
        return null;
      }

      var iterators = forStatement.IteratorExpressions.ToList();
      if (iterators.Count() != 1)
      {
        return null;
      }

      var postfixOperatorExpression = iterators.FirstOrDefault() as PostFixOperatorExpression;
      if (postfixOperatorExpression == null)
      {
        return null;
      }

      return new Model
      {
        ExpressionInitializer = expressionInitializer,
        To = to,
        From = from,
        RelationalExpression = relationalExpression,
        PostfixOperatorExpression = postfixOperatorExpression
      };
    }

    #endregion

    /// <summary>Defines the <see cref="Model"/> class.</summary>
    private class Model
    {
      #region Properties

      /// <summary>
      /// Gets or sets the expression initializer.
      /// </summary>
      /// <value>The expression initializer.</value>
      [NotNull]
      public ExpressionInitializer ExpressionInitializer { get; set; }

      /// <summary>
      /// Gets or sets from.
      /// </summary>
      /// <value>From.</value>
      [NotNull]
      public Expression From { get; set; }

      /// <summary>
      /// Gets or sets the postfix operator expression.
      /// </summary>
      /// <value>The postfix operator expression.</value>
      [NotNull]
      public PostFixOperatorExpression PostfixOperatorExpression { get; set; }

      /// <summary>
      /// Gets or sets the relational expression.
      /// </summary>
      /// <value>The relational expression.</value>
      [NotNull]
      public RelationalExpression RelationalExpression { get; set; }

      /// <summary>
      /// Gets or sets to.
      /// </summary>
      /// <value>To.</value>
      [NotNull]
      public Expression To { get; set; }

      #endregion
    }
  }
}